﻿using System;
using System.Collections.Generic;
#if DEBUG
using System.Diagnostics;
#endif // #if DEBUG
using System.Linq;
using System.Text;
using System.Data.Objects.DataClasses;

using FRL.DebtChased.Repository.ViewModels;
using FRL.DebtChased.Types;
using FRL.DebtChased.WebServices;

namespace FRL.DebtChased.Repository
{

	/// <summary>
	/// Extensions to the Account model
	/// </summary>
	public partial class Account
	{

		/////////////////////////////////////////////////////////////
		// Operations

		public Money CalcTotalAmount()
		{

			// Declare return variable
			Money Ftmp = null;

			// Loop through invoices
			for( int i1=0; i1<this.AccountInvoices.Count; i1++ )
			{
				AccountInvoice inv = this.AccountInvoices.ElementAt(i1);
				if(i1==0)
					Ftmp = new Money(inv.ToMoneyDecimal());
				else
					Ftmp += inv.ToMoneyDecimal();
			}

			// Return variable
			return Ftmp;

		}

		/////////////////////////////////////////////////////////////
		// Static operations

		public static List<string> AddNew(
			DebtChasedEntities ent, 
			AccountTransport newAccountTransport,
			int iUserId
		)
		{
#if DEBUG
			Debug.Assert(newAccountTransport.DebtorContacts != null);
			Debug.Assert(newAccountTransport.DebtorContacts.Count > 0);
#endif // #if DEBUG

			// Declare return variable
			List<string> Ftmp = new List<string>();

			// Declaration of local variables
			bool bFound;
			Account newAccount = null;

			// Check the account details
			if (newAccountTransport.PrimaryClient.ClientId == 0)
				Ftmp.Add("Account must have a primary client contact.");
			if (string.IsNullOrEmpty(newAccountTransport.ClientReference))
				Ftmp.Add("Account has no client reference.");
			if (string.IsNullOrEmpty(newAccountTransport.DebtorName))
				Ftmp.Add("Account has no description.");
			if (string.IsNullOrEmpty(newAccountTransport.FRLReference))
				Ftmp.Add("Account has no FRL reference.");
			else
			{

				// Check that the client reference is unique
				bFound = ((from a in ent.Accounts where (a.FRLReference == newAccountTransport.FRLReference) select a.AccountId).Count() > 0);
				if(bFound)
					Ftmp.Add(string.Format("The account FRL reference \"{0}\" is already in use.", newAccountTransport.FRLReference));

			}
			foreach (AccountInvoiceTransport inv in newAccountTransport.AccountInvoices)
			{
				if(string.IsNullOrEmpty(inv.InvoiceNo))
					Ftmp.Add("One of the invoices has no invoice number.");
				else
				{

					// Check that the invoice number is unique
					bFound = ((from ai in ent.AccountInvoices
							   where (ai.InvoiceNo == inv.InvoiceNo) && (ai.Account.ClientId == newAccountTransport.PrimaryClient.ClientId)
								select ai.AccountInvoiceId).Count() > 0);
					if (bFound)
						Ftmp.Add(string.Format("The invoice number \"{0}\" is already in use for this client.", inv.InvoiceNo));

					// Check invoice amount
					if(inv.Amount.Amount < 0)
						Ftmp.Add(string.Format("Invoice {0} has a zero or negative amount.",inv.InvoiceNo));

				}
			}
			if (newAccountTransport.AccountInvoices.Count == 0)
				Ftmp.Add("Account must have one or more invoices.");

			try
			{

				// If that worked...
				if(Ftmp.Count == 0)
				{

					// Create the account
					newAccount = ent.Accounts.CreateObject();
					newAccount.AssignedUserId = newAccountTransport.CollectorId == 0 ? default(int?) : newAccountTransport.CollectorId;
					newAccount.AssignedManagerId = newAccountTransport.ManagerId == 0 ? default(int?) : newAccountTransport.ManagerId;
					newAccount.ClientId = newAccountTransport.PrimaryClient.ClientId;
					newAccount.ClientContactId = newAccountTransport.PrimaryClient.PrimaryContact.ContactId;
					newAccount.ClientReference = newAccountTransport.ClientReference;
					newAccount.FRLReference = newAccountTransport.FRLReference;
					newAccount.DebtorName = newAccountTransport.DebtorName;
					newAccount.AccountTradingStyleId = newAccountTransport.TradingStyle == 0 ? default(int?) : newAccountTransport.TradingStyle;
					if (newAccountTransport.ValidatedBy != 0)
					{
						newAccount.ValidatedBy = newAccountTransport.ValidatedBy;
						newAccount.ValidatedAt = newAccountTransport.ValidatedAt;
					}
					ent.Accounts.AddObject(newAccount);

					// Add a new message
					AccountNote newAccountNote = ent.AccountNotes.CreateObject();
					newAccountNote.Account = newAccount;
					newAccountNote.Timestamp = DateTime.Now;
					newAccountNote.UserId = iUserId;
					newAccountNote.Narrative = "New account created";
					ent.AccountNotes.AddObject(newAccountNote);

					// Create the invoices
					foreach (AccountInvoiceTransport inv in newAccountTransport.AccountInvoices)
					{

						// Create an invoice object
						AccountInvoice newAccountInvoice = ent.AccountInvoices.CreateObject();
						newAccountInvoice.Account = newAccount;
						newAccountInvoice.AmountValue = inv.Amount.Amount;
						newAccountInvoice.AmountCurrency = inv.Amount.CurrencyCode;
						newAccountInvoice.InvoiceNo = inv.InvoiceNo;
						newAccountInvoice.AccountInvoiceStatusId = inv.Status == 0 ? default(int?) : inv.Status;
						newAccountInvoice.WorkflowId = inv.WorkflowId == 0 ? default(int?) : inv.WorkflowId;
						newAccountInvoice.WorkflowDay1 = inv.WorkflowDay1;
						newAccountInvoice.InvoiceDate = inv.InvoiceDate;
						newAccount.AccountInvoices.Add(newAccountInvoice);

						// Connect invoice to note
						AccountNoteAccountInvoice newAccountNoteInvoice = ent.AccountNoteAccountInvoices.CreateObject();
						newAccountNoteInvoice.AccountInvoice = newAccountInvoice;
						newAccountNoteInvoice.AccountNote = newAccountNote;
						newAccountNote.AccountNoteAccountInvoices.Add(newAccountNoteInvoice);

					}

					// Update contacts
					AccountContact.InterCursorSetFromTransport(ent, newAccount, newAccountTransport, iUserId);

				}

				// If that worked...
				if(Ftmp.Count==0)
				{

					// Save to repository
					ent.SaveChanges();

				}

			}
			catch (Exception x)
			{

				// Process the exception
				string strError = ErrorProcessing.HandleException(x, "create account", iUserId);

				// Update return variable
				Ftmp.Add(strError);

			}

			// Return variable
			return Ftmp;

		}

		public static string Obliterate( DebtChasedEntities ent, int iAccountId, int? iUserId )
		{

			// Declare return variable
			string strFtmp = null;

			// Declaration of local variables
			int i1 = 0;

			// Load the account from the repository
			var acc = (from a in ent.Accounts.Include("AccountInvoices").Include("AccountNotes").Include("AccountContacts")
					  where (a.AccountId == iAccountId)
					  select a).FirstOrDefault();

			// If found...
			if (acc != null)
			{

				try
				{

					// Remove invoice note associations
					List<AccountNoteAccountInvoice> assocList = (from anai in ent.AccountNoteAccountInvoices
																join an in ent.AccountNotes on anai.AccountNoteId equals an.AccountNoteId
																where an.AccountId == iAccountId
																select anai).ToList();
					for (i1 = 0; i1 < assocList.Count; i1++)
						ent.AccountNoteAccountInvoices.DeleteObject(assocList[i1]);

					// Remove account invoices
					List<AccountInvoice> invoices = acc.AccountInvoices.ToList();
					for (i1 = 0; i1 < invoices.Count; i1++)
						ent.AccountInvoices.DeleteObject(invoices[i1]);

					// Remove account notes
					List<AccountNote> notes = acc.AccountNotes.ToList();
					for (i1 = 0; i1 < notes.Count; i1++)
						ent.AccountNotes.DeleteObject(notes[i1]);

					// Remove the contacts
					List<AccountContact> contacts = acc.AccountContacts.ToList();
					for (i1 = 0; i1 < contacts.Count; i1++)
						ent.AccountContacts.DeleteObject(contacts[i1]);

					// Remove the account
					ent.DeleteObject(acc);

					// Commit
					ent.SaveChanges();

				}
				catch (Exception x)
				{

					// Update return variable
					strFtmp = ErrorProcessing.HandleException(x, "delete account", iUserId);

				}

			}
			else
				strFtmp = "Account not found.";

			// Return variable
			return strFtmp;

		}

		public static List<AccountTransport> LoadAccountTransport(DebtChasedEntities ent, DateTime dtFrom, DateTime dtTo, string strClientPartial)
		{

			// Declare return variable
			List<AccountTransport> Ftmp = new List<AccountTransport>();

			// Loop through accounts
			var accounts = from a in ent.Accounts.Include("AccountContacts").Include("AccountInvoices") select a;
			foreach (Account acc in accounts)
			{

				// Load an account transport
				AccountTransport at = _accountTransportFactory(ent, acc);
				Ftmp.Add(at);

			}

			// Return variable
			return Ftmp;

		}

		public static AccountTransport LoadAccountTransport( DebtChasedEntities ent, int iAccountId )
		{

			// Declare return variable
			AccountTransport Ftmp = null;

			// Loop through accounts
			var account = (from a in ent.Accounts.Include("AccountContacts").Include("AccountInvoices") 
						   where a.AccountId==iAccountId 
						   select a).FirstOrDefault();
			if(account != null)
			{

				// Load an account transport
				Ftmp = _accountTransportFactory(ent, account);

			}

			// Return variable
			return Ftmp;

		}

		private static AccountTransport _accountTransportFactory(DebtChasedEntities ent, Account acc)
		{

			// Declare return variable
			AccountTransport Ftmp = new AccountTransport()
			{
				AccountId = acc.AccountId,
				FRLReference = acc.FRLReference,
				ClientReference = acc.ClientReference,
				DebtorName = acc.DebtorName,
				DebtorClientRef = acc.DebtorClientReference,
				TradingStyle = acc.AccountTradingStyleId ?? 0,
				CollectorId = acc.AssignedUserId ?? 0,
				ManagerId = acc.AssignedManagerId ?? 0,
				ValidatedAt = acc.ValidatedAt,
				ValidatedBy = acc.ValidatedBy ?? 0
			};

			// Load debtor contacts
			Ftmp.DebtorContacts = AccountContact.GetTransportList(acc);

			// Load client details
			Ftmp.PrimaryClient = Client.GetPrimaryClientTransport( ent, acc.ClientId, acc.ClientContactId );

			// TODO
			// Load agent details

			// Load plan details
			Ftmp.ActiveAccountPlan = AccountPlan.GetActiveTransport(ent, acc.AccountId);

			// Load account invoices
			Ftmp.AccountInvoices = new List<AccountInvoiceTransport>();
			foreach(AccountInvoice inv in acc.AccountInvoices)			
			{
				AccountInvoiceTransport ait = new AccountInvoiceTransport()
				{
					AccountInvoiceId = inv.AccountInvoiceId,
					Amount = new CurrencyTransport() { Amount=inv.AmountValue, CurrencyCode=inv.AmountCurrency},
					InvoiceDate = inv.InvoiceDate,
					Status = inv.AccountInvoiceStatusId ?? 0,
					InvoiceNo = inv.InvoiceNo,
					WorkflowId = inv.WorkflowId ?? 0,
					WorkflowDay1 = inv.WorkflowDay1,
					AssociatedWithChase = false					
				};
				if (inv.AccountInvoiceStatus == null)
					ait.IsInQuery = false;
				else
					ait.IsInQuery = inv.AccountInvoiceStatus.IsInQuery;
				Ftmp.AccountInvoices.Add(ait);
			}

			// Load account notes
			Ftmp.Notes = new List<AccountNoteTransport>();
			var notes = from an in ent.AccountNotes
						where (an.AccountId == acc.AccountId)
						orderby an.Timestamp
						select new { 
							an, 
							UserFullName=an.User.FinanceStaffContact.FullName, 
							InvoiceIds=an.AccountNoteAccountInvoices 
						};
			foreach(var note in notes)
			{
				AccountNoteTransport newNote = new AccountNoteTransport() { 
					AccountNoteId = note.an.AccountNoteId,
					UserDescription = note.UserFullName,
					UserId = note.an.UserId ?? 0,
					Narrative = note.an.Narrative,
					Error = note.an.Error,
					Timestamp = note.an.Timestamp,
					Flags = note.an.fdFlags
				};
				newNote.AccountInvoiceIds = new List<int>();
				foreach(var anai in note.InvoiceIds)
					newNote.AccountInvoiceIds.Add(anai.AccountInvoiceId);
				Ftmp.Notes.Add(newNote);
			}

			// Return variable
			return Ftmp;

		}

		public static Account GetNextNewValidatedAccount( DebtChasedEntities ent )
		{

			// Declare return variable
			Account Ftmp = null;

			// Load the valid accounts with null invoice status
			var result = (from a in ent.Accounts
						  join ai in ent.AccountInvoices on a.AccountId equals ai.AccountId
						  where (ai.AccountInvoiceStatusId==null) && (a.ValidatedBy!=null)
						  group a by a.AccountId into g
						  select new { AccountId=g.Key, Count=g.Count() }).FirstOrDefault();

			// If account found...
			if (result != null)
			{

				// Load the account
				Ftmp = ent.Accounts.First(a => a.AccountId == result.AccountId);

			}

			// Return variable
			return Ftmp;

		}

		public static Account GetNextAccountForWorkflowProcessing(DebtChasedEntities ent)
		{

			// Declare return variable
			Account Ftmp = null;

			// Declaration of local variables
			DateTime dtNow = DateTime.Now;

			// To be processed, account must have invoice must have:
			// a valid workflow-id
			// AND a null or expired next-processing-date
			int iAccountId = (from ai in ent.AccountInvoices
							  where 
							  (
								(ai.WorkflowId != null)
								&&
								(
									(ai.NextWorkflowProcessingTime == null)
									||
									(ai.NextWorkflowProcessingTime <= dtNow)
								)
							  )
							  select ai.AccountId).FirstOrDefault();

			// If account found...
			if (iAccountId != 0)
			{

				// Load the account
				Ftmp = ent.Accounts.First(a => a.AccountId == iAccountId);

			}

			// Return variable
			return Ftmp;

		}

		public static List<ContactTransport> GetContacts( DebtChasedEntities ent, int iAccountId, Definitions.eCommTargetType targetType )
		{

			// Declaration of local variables
			List<ContactTransport> Ftmp = new List<ContactTransport>();

			// Declaration of local variables
			ContactTransport ct = null;

			// Compare target types
			switch (targetType)
			{
				case Definitions.eCommTargetType.Agent:
				case Definitions.eCommTargetType.Solicitor:
					int? iAgentId = null;
					if(targetType == Definitions.eCommTargetType.Agent)
						iAgentId = (from a in ent.Accounts where (a.AccountId == iAccountId) select a.AgentId).First();
					else
						iAgentId = (from a in ent.Accounts where (a.AccountId == iAccountId) select a.SolicitorId).First();
					if(iAgentId != null)
					{
#if DEBUG
						Debug.Assert(false); // todo
#endif // #if DEBUG
					}
					break;
				case Definitions.eCommTargetType.Client:
					int iClientContactId = (from a in ent.Accounts where a.AccountId == iAccountId select a.ClientContactId).FirstOrDefault();
					if(iClientContactId != 0)
					{
						ct = ClientContact.GetTransport(ent,iClientContactId);
						if(ct != null)
							Ftmp.Add(ct);
					}
					break;
				case Definitions.eCommTargetType.Debtor:
					Ftmp = AccountContact.GetTransportList(ent,iAccountId);
					break;
				default:
#if DEBUG
					Debug.Assert(false);
#endif // #if DEBUG
					break;
			} // switch

			// Return variable
			return Ftmp;

		}

		public static List<ChaseSessionItemTransport> LoadChaseSessionTransport(DebtChasedEntities ent, int iUserId)
		{

			// Declare return variable
			List<ChaseSessionItemTransport> Ftmp = new List<ChaseSessionItemTransport>();

			// Load accounts for this user
			var accounts = from a in ent.Accounts.Include("AccountInvoices")
						   select new { 
							   AccountId = a.AccountId,
							   FRLReference = a.FRLReference,
							   Invoices = a.AccountInvoices
						   };
			foreach (var account in accounts)
			{

				// Add new chase session item transport
				ChaseSessionItemTransport chaseItem = new ChaseSessionItemTransport() {
					AccountId = account.AccountId,
					FRLAccountRef = account.FRLReference
				};
				Ftmp.Add(chaseItem);

				// Loop through invoices, build priority list, build chase reason list, calculate total
				chaseItem.PriorityList = new List<int>();
				chaseItem.ChaseReasonList = new List<string>();
				List<Money> mnList = new List<Money>();
				foreach (AccountInvoice inv in account.Invoices)
				{

					// Get priorty and chase reason for this invoice
					int iPriority = (int)WorkflowObjects.WorkflowAction_Base.ePriority.Low;
					string strChaseReason = "Text Account";

					// Add to lists
					if (!chaseItem.PriorityList.Contains(iPriority))
						chaseItem.PriorityList.Add(iPriority);
					if (!chaseItem.ChaseReasonList.Contains(strChaseReason))
						chaseItem.ChaseReasonList.Add(strChaseReason);

					// Get amount for total
					mnList.Add( new Money( inv.AmountValue, inv.AmountCurrency) );

				}

				// Update total text
				chaseItem.TotalAsText = Money.SuperSmartAddition(mnList,false);

			}

			// Return variable
			return Ftmp;

		}

		public static AccountTransport GetTransport(DebtChasedEntities ent, int iAccountId)
		{

			// Declare return variable
			AccountTransport Ftmp = null;

			// Load the account
			var account = (from a in ent.Accounts.Include("AccountContacts").Include("AccountInvoices")
						  where a.AccountId == iAccountId
						  select a).FirstOrDefault();
			if (account != null)
				Ftmp = _accountTransportFactory(ent, account);

			// Return variable
			return Ftmp;

		}

	}

}
